package fun

import (
	"errors"
	"fmt"
	"strings"
	"time"
)

const (
	GO_TIME              = "2006-01-02 15:04:05"
	GO_DATE              = "2006-01-02"
	GO_DATE_NUMBER       = "20060102"
	GO_YEAR_MONTH        = "2006-01"
	GO_YEAR_MONTH_NUMBER = "200601"
)

type CountdownDto struct {
	DistanceType string `json:"distance_type"`
	SecondsLeft  int    `json:"seconds_left"`
	Days         int    `json:"days"`
	Hours        int    `json:"hours"`
	Minutes      int    `json:"minutes"`
	Seconds      int    `json:"seconds"`
}

// @title: Rfc3339格式时间转为正常时间
// @param: Rfc3339时间字符(string)
// @return: 时间字符串(string)
// @description: 如 2018-01-14T21:45:54+08:00 转为 2018-01-14 21:45:54
// @date: 2024/6/11 22:32
func Rfc3339ToDatetime(str string) string {
	tt, _ := time.Parse(time.RFC3339, str)
	return tt.Format(GO_TIME)
}

// @title: 标准时间转为时间戳
// @param: 标准时间(string)
// @return: 时间戳（秒）(int64)
// @description: 如 2018-01-14 21:45:54 转为 1515937554
// @date: 2024/6/11 22:32
func DatetimeToTimestamp(str string) int64 {
	loc, err := time.LoadLocation("UTC") //获取时区
	if err != nil {
		return 0
	}
	tmp, err := time.ParseInLocation(GO_TIME, str, loc)
	if err != nil {
		return 0
	}
	timestamp := tmp.Unix() //转化为时间戳 类型是int64
	return timestamp
}

// @title: 时间戳转为标准时间
// @param: 时间戳(int64)
// @return: 标准时间(string)
// @description: 如 1515937554 转为 2018-01-14 21:45:54
// @date: 2024/6/11 22:32
func TimestampToDatetime(timestamp int64) string {
	return time.Unix(timestamp, 0).Format(GO_TIME)
}

// @title: 获取当前标准时间
// @param:
// @return: 标准时间(string)
// @description: 时间格式：2021-12-30 23:14:07
// @date: 2024/6/11 22:32
func CurrentDateTime() string {
	return time.Now().Format(GO_TIME)
}

// @title: 获取当前时间戳（秒）
// @param:
// @return: 时间戳(int64)
// @description:
// @date: 2024/6/11 22:32
func TimeStampSecond() int64 {
	return time.Now().Unix()
}

// @title: 获取时间戳（毫秒）
// @param:
// @return: 时间戳(int64)
// @description:
// @date: 2024/6/11 22:32
func Millisecond() int64 {
	return time.Now().UnixNano() / 1e6
}

// @title: 一个字符串时间与当前时间的时间差
// @param: 字符串时间(string) 如：2022-05-22 23:59:59
// @return: time.Duration，error
// @description: 东八时区
// @date: 2024/6/11 22:32
func DateTimeStrAadNowSub(ts string) (time.Duration, error) {
	// 本地时间
	now := time.Now()

	// 按照指定格式解析一个字符串格式的时间
	_, err := time.Parse(GO_TIME, ts)
	if err != nil {
		return 0, err
	}

	// 按照东八区的时区格式解析一个字符串
	tlocal, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		return 0, err
	}

	// 按照指定的时区解析时间
	t, err := time.ParseInLocation(GO_TIME, ts, tlocal)
	if err != nil {
		return 0, err
	}

	// 计算时间的差值
	//reverseTime := now.Sub(t)
	reverseTime := t.Sub(now)

	return reverseTime, nil
}

// @title: 时间戳转日期
// @param: int64，*time.Location
// @return: string
// @description: 输出日期格式(2006-01-02)
// @date: 2024/6/11 22:32
func TimestampToDate(timestamp int64, loc *time.Location) string {
	tm := time.Unix(timestamp, 0).In(loc) // time.UTC
	return tm.Format(GO_DATE)
}

// @title: 时间戳转数字格式日期字符
// @param: int64，*time.Location
// @return: string
// @description: 输出日期格式(20060102)
// @date: 2024/6/11 22:32
func TimestampToDateNumber(timestamp int64, loc *time.Location) string {
	tm := time.Unix(timestamp, 0).In(time.UTC) // time.UTC
	return tm.Format(GO_DATE_NUMBER)
}

// @title: 获取时区-东八区(中国上海)
// @param:
// @return: *time.Location
// @description:
// @date: 2024/6/11 22:32
func LocAsiaShanghai() *time.Location {
	loc, _ := time.LoadLocation("Asia/Shanghai")
	return loc
}

// @title: 获取时区-0时区
// @param:
// @return: *time.Location
// @description:
// @date: 2024/6/11 22:32
func LocUTC() *time.Location {
	return time.UTC
}

// @title: 获取时区-西八区(洛杉矶)
// @param:
// @return: *time.Location
// @description:
// @date: 2024/6/11 22:32
func LocAmericaLosAngeles() *time.Location {
	loc, _ := time.LoadLocation("America/Los_Angeles")
	return loc
}

// @title: 获取某个时间起，N天内的连续的数字格式日期
// @param: t:某个时间
// @param: days:相隔N天
// @param: dayN:取多少天
// @param: SameDay:当天是否在内
// @return: []time.Time
// @description: 返回格式如 [20240718 20240719 20240720 20240721 20240722 20240723 20240724]
// @date: 2024/6/11 22:32
func GetSometimeApartNDaysTimes(t time.Time, days int, dayN int, SameDay bool) []time.Time {
	list := make([]time.Time, 0, dayN)
	I := 0
	if SameDay {
		I = 1
		list = append(list, t)
	}
	for i := I; i < dayN; i++ {
		newTime := t.AddDate(0, 0, days)
		list = append(list, newTime)
		t = newTime
	}
	return list
}

// @title: 获取某个时间起，N天内的连续的时间（指定日期格式）
// @param: t:某个时间
// @param: days:相隔N天
// @param: dayN:取多少天
// @param: SameDay:当天是否在内
// @param: format:日期格式（如:2006-01-02 15:04:05）
// @return: []time.Time
// @description:
// @date: 2024/6/11 22:32
func GetSometimeApartNDaysTimesFormat(t time.Time, days int, dayN int, SameDay bool, format string) []string {
	list := GetSometimeApartNDaysTimes(t, days, dayN, SameDay)
	newList := make([]string, 0, len(list))
	for _, t := range list {
		date := t.Format(format)
		newList = append(newList, date)
	}

	return newList
}

// @title: 获取某个时间起，第N天内的时间（指定日期格式）
// @param: t:某个时间
// @param: days:相隔N天
// @param: format:日期格式（如:2006-01-02 15:04:05）
// @return: string
// @description: 可以用来获取明天、昨天日期
// @date: 2024/6/11 22:32
func GetSometimeApartNDaysTimeFormat(t time.Time, days int, format string) string {
	newT := t.AddDate(0, 0, days)
	str := newT.Format(format)
	return str
}

// @title: 验证是否日期格式(yyyy-mm-dd)
// @param: 日期（如:2006-01-02）
// @return: bool
// @description:
// @date: 2024/6/11 22:32
func ValidateDate(dateStr string) bool {
	_, err := time.Parse(GO_DATE, dateStr)
	return err == nil
}

// @title: 两日期相隔天数
// @param: 开始日期（如:2006-01-02）,结束日期（如:2006-01-02）
// @return: int, error
// @description:
// @date: 2024/6/11 22:32
func TwoDateDaysApart(startDate, endDate string) (int, error) {
	// 解析开始日期
	start, err := time.Parse(GO_DATE, startDate)
	if err != nil {
		return 0, fmt.Errorf("start date parsing failed: %v", err)
	}

	// 解析结束日期
	end, err := time.Parse(GO_DATE, endDate)
	if err != nil {
		return 0, fmt.Errorf("end date parsing failed: %v", err)
	}

	if start.After(end) {
		return 0, errors.New("startDate greater than endDate")
	}

	// 计算差值
	diff := end.Sub(start)

	// 返回相差的整天数
	return int(diff.Hours() / 24), nil
}

// @title: 两日期间从第几天起取n个连续的日期
// @param: 开始日期（如:2006-01-02）,结束日期（如:2006-01-02）,开始索引,取多少个
// @return: int64, []string, error
// @description:
// @date: 2024/6/11 22:32
func TwoDateGetDatesFromIndex(startDateStr, endDateStr string, index int64, n int64) (int64, []string, error) {
	startDate, err := time.Parse(GO_DATE, startDateStr)
	if err != nil {
		return 0, nil, fmt.Errorf("failed to parse start date: %w", err)
	}

	endDate, err := time.Parse(GO_DATE, endDateStr)
	if err != nil {
		return 0, nil, fmt.Errorf("failed to parse end date: %w", err)
	}

	if startDate.After(endDate) {
		return 0, nil, errors.New("startDate greater than endDate")
	}

	diff := endDate.Sub(startDate)
	totalDays := int64(diff.Hours() / 24)

	actualStartDate := startDate.AddDate(0, 0, int(index))
	var dates []string
	nI := int(n)
	for i := 0; i < nI; i++ {
		date := actualStartDate.AddDate(0, 0, i)
		if date.After(endDate) {
			break
		}
		formattedDate := date.Format(GO_DATE)
		dates = append(dates, formattedDate)
	}

	return totalDays, dates, nil
}

// @title: 从两日期间获取连续的日期
// @param: 开始日期（如:2006-01-02）,结束日期（如:2006-01-02）
// @return: []string, error
// @description:
// @date: 2024/6/11 22:32
func TwoDateGetDates(startDateStr, endDateStr string) ([]string, error) {
	startDate, err := time.Parse(GO_DATE, startDateStr)
	if err != nil {
		return nil, fmt.Errorf("failed to parse start date: %w", err)
	}

	endDate, err := time.Parse(GO_DATE, endDateStr)
	if err != nil {
		return nil, fmt.Errorf("failed to parse end date: %w", err)
	}

	if startDate.After(endDate) {
		return nil, errors.New("startDate greater than endDate")
	}

	var dates []string

	for currentDate := startDate; !currentDate.After(endDate); currentDate = currentDate.AddDate(0, 0, 1) {
		dates = append(dates, currentDate.Format(GO_DATE))
	}

	return dates, nil
}

// @title: 获取某个日期的前几天连续日期
// @param: 日期（如:2006-01-02）， 天数（3）
// @return: []string, error
// @description:
// @date: 2024/6/11 22:32
func GetPreviousFewDaysDates(dateStr string, n int) ([]string, error) {
	// 解析输入的日期字符串为time.Time类型
	date, err := time.Parse(GO_DATE, dateStr)
	if err != nil {
		return nil, err
	}

	var days []string
	// 循环n次，分别计算每一天的日期
	for i := 1; i <= n; i++ {
		day := date.AddDate(0, 0, -i)
		days = append(days, day.Format(GO_DATE))
	}
	//// 因为是从date开始倒序添加，所以需要反转切片
	for i, j := 0, len(days)-1; i < j; i, j = i+1, j-1 {
		days[i], days[j] = days[j], days[i]
	}
	return days, nil
}

// @title: 某个日期是周几(星期几)
// @param: 日期（如:2006-01-02）
// @return: time.Weekday, error
// @description:
// @date: 2024/6/11 22:32
func GetWeekdayByDate(date string) (time.Weekday, error) {
	parsedTime, err := time.Parse(GO_DATE, date)
	if err != nil {
		return 0, errors.New("date wrong")
	}

	weekday := parsedTime.Weekday()
	return weekday, nil
}

// @title: 获取某个日期的开始及结束日期
// @param: 日期（如:2006-01-02）
// @return: string, string
// @description:
// @date: 2024/6/11 22:32
func GetWeekStartEndByDate(date string) (string, string) {
	parsedTime, err := time.Parse(GO_DATE, date)
	if err != nil {
		return "", ""
	}

	// 获取当前日期是星期几 (Sunday=0, Monday=1, ..., Saturday=6)
	weekday := parsedTime.Weekday()

	// 计算该周的开始日期（周一）
	var startOfWeek time.Time
	if weekday == 0 { // 如果是周日，需要减去 6 天
		startOfWeek = parsedTime.AddDate(0, 0, -6)
	} else {
		startOfWeek = parsedTime.AddDate(0, 0, -(int(weekday) - 1))
	}

	// 计算该周的结束日期（周日）
	endOfWeek := startOfWeek.AddDate(0, 0, 6)

	// 格式化为字符串返回
	return startOfWeek.Format(GO_DATE), endOfWeek.Format(GO_DATE)
}

// @title: 判断date1是否大于date2
// @param: 日期（如:2006-01-02）
// @return: bool, error
// @description:
// @date: 2024/6/11 22:32
func IsDate1GreaterThanDate2(date1Str, date2Str string) (bool, error) {
	// 解析日期字符串为 time.Time 类型
	date1, err := time.Parse(GO_DATE, date1Str)
	if err != nil {
		return false, fmt.Errorf("date1 wrong: %v", err)
	}

	date2, err := time.Parse(GO_DATE, date2Str)
	if err != nil {
		return false, fmt.Errorf("date2 wrong: %v", err)
	}

	// 比较两个日期
	return date1.After(date2), nil
}

// @title: 获取某个日期的月份的最后一天
// @param: 日期（如:2006-01-02）
// @return: string, error
// @description:
// @date: 2024/6/11 22:32
func GetLastDayOfMonthByDate(dateStr string) (string, error) {
	// 解析日期字符串为 time.Time 类型
	date, err := time.Parse(GO_DATE, dateStr)
	if err != nil {
		return "", fmt.Errorf("date wrong: %v", err)
	}

	// 获取下个月的第一天
	nextMonth := time.Date(date.Year(), date.Month()+1, 1, 0, 0, 0, 0, date.Location())

	// 计算当前月的最后一天（下个月第一天减去 1 天）
	lastDayOfMonth := nextMonth.AddDate(0, 0, -1)

	// 格式化为字符串返回
	return lastDayOfMonth.Format(GO_DATE), nil
}

// @title: 获取月份的最后一天
// @return: string
// @description:
// @date: 2024/6/11 22:32
func GetMonthLastDate() string {
	now := time.Now()
	// 获取下个月的第一天
	nextMonth := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location())
	// 计算当前月的最后一天（下个月第一天减去 1 天）
	lastDayOfMonth := nextMonth.AddDate(0, 0, -1)
	// 格式化为字符串返回
	return lastDayOfMonth.Format("2006-01-02")
}

// @title: 获取周的最后一天
// @return: string
// @description:
// @date: 2024/6/11 22:32
func GetWeekLastDate() string {
	now := time.Now()
	weekday := now.Weekday()

	// 计算该周的开始日期（周一）
	var startOfWeek time.Time
	if weekday == 0 { // 如果是周日，需要减去 6 天
		startOfWeek = now.AddDate(0, 0, -6)
	} else {
		startOfWeek = now.AddDate(0, 0, -(int(weekday) - 1))
	}

	// 计算该周的结束日期（周日）
	endOfWeek := startOfWeek.AddDate(0, 0, 6)

	return endOfWeek.Format("2006-01-02")
}

// @title: 获取某个日期的年份和第几周
// @param: 日期（如:2006-01-02）
// @return: int, int, error
// @description:
// @date: 2024/6/11 22:32
func GetYearWeekByDate(date string) (int, int, error) {
	parsedDate, err := time.Parse(GO_DATE, date)
	if err != nil {
		return 0, 0, errors.New("date wrong")
	}

	// 使用 ISOWeek 获取年份和对应的周数
	year, week := parsedDate.ISOWeek()

	return year, week, nil
}

// @title: 获取上一周开始、结束日期
// @param: 时间
// @return: start, end
// @description:
// @date: 2024/6/11 22:32
func GetLastWeek(t time.Time) (start, end time.Time) {
	// Go语言中的time.Weekday()方法将Sunday视为一周的第一天(值为0)，因此我们需要调整。
	// 首先获取当前时间对应的星期几，并将其转换为以周一为一周第一天的情况
	weekday := t.Weekday()
	if weekday == time.Sunday {
		weekday = 7 // 如果是周日，则将其视为第7天（即一周的最后一天）
	}

	// 计算距离上周日需要回退几天
	daysUntilLastSunday := int(weekday)
	lastSunday := t.Add(-time.Duration(daysUntilLastSunday) * 24 * time.Hour)

	// 上周一就是上周日往前推6天
	lastMonday := lastSunday.Add(-6 * 24 * time.Hour)

	// 设置开始时间为周一 00:00:00
	start = time.Date(lastMonday.Year(), lastMonday.Month(), lastMonday.Day(), 0, 0, 0, 0, t.Location())

	// 设置结束时间为周日 23:59:59
	end = time.Date(lastSunday.Year(), lastSunday.Month(), lastSunday.Day(), 23, 59, 59, int(time.Second-time.Nanosecond), t.Location())

	return start, end
}

// @title: 获取某个时间的前N天或后N天的日期
// @param: 时间
// @return: string
// @description:
// @date: 2024/6/11 22:32
func GetNDayDateByTime(t time.Time, n int) string {
	t0 := t.AddDate(0, 0, n)
	return t0.Format(GO_DATE)
}

// @title: 获取某个日期的前N天或后N天的日期
// @param: 时间
// @return: string
// @description:
// @date: 2024/6/11 22:32
func GetNDayDateByDate(dateStr string, n int) (string, error) {
	// 解析字符串日期为 time.Time 类型
	parsedDate, err := time.Parse(GO_DATE, dateStr)
	if err != nil {
		return "", fmt.Errorf("failed to parse date: %v", err)
	}

	// 添加 N 天
	newDate := parsedDate.AddDate(0, 0, n)

	// 格式化为字符串并返回
	return newDate.Format(GO_DATE), nil
}

// @title: 当天还剩多少秒
// @param: 时间
// @return: string
// @description:
// @date: 2024/6/11 22:32
func SameDaySecondsAreLeft() int {
	now := time.Now()
	endOfDay := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
	duration := endOfDay.Sub(now)
	secondsLeft := int(duration.Seconds())
	return secondsLeft
}

// @title: 距离某星期几的时间
// @param: 时间
// @return: string
// @description: 如当天是星期一，传参周一(当天)，距离结束时间还剩多少秒；
// @description: 如当天是星期一，传参周二(时间未过)，距离这周二时间还差多少秒；
// @description: 如当天是星期二，传参周一(时间已过，取下周时间)，距离下周一还差多少秒；
// @date: 2024/6/11 22:32
func DistanceStartEndTimeByWeekName(name string) (countdown CountdownDto, err error) {
	name = strings.ToLower(name)
	weekdayMap := map[string]time.Weekday{
		"monday":    time.Monday,
		"tuesday":   time.Tuesday,
		"wednesday": time.Wednesday,
		"thursday":  time.Thursday,
		"friday":    time.Friday,
		"saturday":  time.Saturday,
		"sunday":    time.Sunday,
	}

	// 检查 rule 是否有效
	targetWeekday, ok := weekdayMap[name]
	if !ok {
		return countdown, errors.New("name wrong")
	}

	// 获取当前时间
	now := time.Now()
	// 获取今天的 Weekday
	currentWeekday := now.Weekday()
	currentWeekdayName := strings.ToLower(currentWeekday.String())
	secondsLeft := 0
	if name == currentWeekdayName {
		countdown.DistanceType = "distance_end" // 距离结束
		secondsLeft = SameDaySecondsAreLeft()
	} else {
		countdown.DistanceType = "distance_start" // 距离开始
		// 计算目标日期与当前日期的天数差
		daysUntilTarget := int(targetWeekday - currentWeekday)
		if daysUntilTarget < 0 {
			// 如果目标日期已经过去，则取下周
			daysUntilTarget += 7
		}

		// 创建目标日期的时间对象
		targetDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, daysUntilTarget)
		targetDate = targetDate.Add(0*time.Hour + 0*time.Minute + 0*time.Second)

		// 计算剩余秒数
		duration := targetDate.Sub(now)
		secondsLeft = int(duration.Seconds())
	}

	// 将秒数转换为天、小时、分钟和秒
	countdown.Days = secondsLeft / (24 * 3600)
	remainingSeconds := secondsLeft % (24 * 3600)
	countdown.Hours = remainingSeconds / 3600
	remainingSeconds %= 3600
	countdown.Minutes = remainingSeconds / 60
	countdown.Seconds = remainingSeconds % 60

	return countdown, nil
}

// @title: 某个时间加减N天
// @param: 时间
// @return: string
// @description:
// @date: 2024/6/11 22:32
func AddDate(timeStr string, n int) (time.Time, string) {
	// 解析时间字符串
	t, err := time.Parse(GO_TIME, timeStr)
	if err != nil {
		return time.Time{}, ""
	}

	// 加上或减去指定天数
	newTime := t.AddDate(0, 0, n)

	return newTime, newTime.Format(GO_TIME)
}
